package rip910;

import java.util.LinkedList;
import vm910.*;

import java.util.HashMap;

public class SampleVisitor implements Visitor, L910Consts {
	
	public VM910 vm;
	public LinkedList<HashMap> ll;
	
	public SampleVisitor(){
		super();
		vm = new VM910(5000,5000);
		ll = new LinkedList<HashMap>();
	}
	
	@Override
	public Object visit(IfStat ifStat){

		
		ifStat.getCondition().accept(this);

		Integer addr1 = (Integer)vm.addInstr(Codes.JUMPF, -99); 
		
		ifStat.getThenPart().accept(this);
		
		Integer addr2 = (Integer)vm.addInstr(Codes.JUMP,-99);
		
 		vm.fixAddr(addr1.intValue(),(addr2.intValue()+1));

 		Integer addr3 = (Integer)ifStat.getElsePart().accept(this);

		vm.fixAddr(addr2.intValue(),(addr3.intValue()+1));

		return addr3;
	}
		
	@Override
	public Object visit(Program prog){
	      Integer lastAddr = (Integer)prog.getBody().accept(this);
	return lastAddr;
	}
	
	public Object visit(DiadOp diadOp){

		diadOp.getOperand1().accept(this);

		diadOp.getOperand2().accept(this);
		
		if(diadOp.getOperand1().getType() != Type.wrongType && diadOp.getOperand2().getType() != Type.wrongType)
			if(diadOp.getOperand1().getType() != diadOp.getOperand2().getType()){
					System.out.println("ERRO NA ANALISE SEMANTICA DE OPERACAO: OS TIPOS DA OPERACAO NAO SAO COMPATIVEIS Tipo 1:"+
							diadOp.getOperand1().getType() + " Tipo 2: " +
							diadOp.getOperand2().getType());
					diadOp.setType(Type.wrongType);
			}else if(diadOp.getType() == null){
				diadOp.setType(diadOp.getOperand1().getType());
			}
			

		int addr = -99;
		switch(diadOp.getKind()){
		case(ADD_OP):  addr = vm.addInstr(Codes.ADD);  break;
		case(SUB_OP): addr = vm.addInstr(Codes.SUB);  break;
		case(MULT_OP): addr = vm.addInstr(Codes.MULT); break;
		case(DIV_OP):  addr = vm.addInstr(Codes.DIV);  break;
		case(AND_OP):  addr = vm.addInstr(Codes.AND);  break;
		case(OR_OP):   addr = vm.addInstr(Codes.OR);  break;
		case(EQ_OP):   addr = vm.addInstr(Codes.EQ);  break;
		case(NE_OP):   addr = vm.addInstr(Codes.NE);  break;
		case(LT_OP):   addr = vm.addInstr(Codes.LT);   break;
		case(LE_OP):   addr = vm.addInstr(Codes.LE);   break;
		case(GT_OP):   addr = vm.addInstr(Codes.GT);   break;
		case(GE_OP):   addr = vm.addInstr(Codes.GE);   break;
		}      
		return new Integer(addr);
	}

	@Override
	public Object visit(CompoundStat c) {
		LinkedList<Declaration> list = c.getDeclarations();
		LinkedList<StatOp> list2 = c.getCommandList();
		int size = 0;
		int index;
		int addrBegin;
		int addr = -99;
		HashMap hm, param;
		boolean isFunction = list2.size() > 0 && list.size() > 0;
		boolean isFunctionDeclaration = list2.size() == 0 && list.size() > 0;
		FuncSemantica func;

		if (list2.size() == 0 && list.size() == 0)
			return new Integer(vm.getPC());
		
		if(isFunction){
			hm = ll.getFirst();
			index = 1;
			for(Declaration d: list){
				size += ((VarDeclList)d).getList().size();
				for(VarDeclaration v: ((VarDeclList)d).getList()){
					hm.put(v.getName(), new VarSemantica(index,d.getType()));
					index++;
				}
			}
			addr = vm.addInstr(Codes.ENTER,size);
		}else if(isFunctionDeclaration){
			
			hm = new HashMap();
			ll.addFirst(hm);
			
			for(Declaration d: list){
				hm.put(((FunctionDeclaration)d).getName(), new FuncSemantica(-99,((FunctionDeclaration)d).getType(),((FunctionDeclaration)d).getParmList()));
			}
			
			if(hm.get("main") == null)
				System.out.println("Nao tem MAIN!");
			
			addrBegin = vm.addInstr(Codes.CALL,((FuncSemantica)hm.get("main")).getAddr());
			addr = ((Integer)vm.addInstr(Codes.HALT)).intValue();
			
			for(Declaration d: list){
				
				param = new HashMap();
				ll.addFirst(param);
				
				if(((FunctionDeclaration)d).getName().equals("main"))
					vm.fixAddr(addrBegin,(addr+1));
				
				func = ((FuncSemantica)hm.get(((FunctionDeclaration)d).getName()));
				func.setAddr(addr+1);
				
				for(int i = 0; i < func.getParam().size(); i++){
					VarDeclaration v = func.getParam().get(i);
					param.put(v.getName(), new VarSemantica(-func.getParam().size()+1 -2 +i,v.getType()));
				}
				
				
				param.put("return", new VarSemantica(-func.getParam().size() -2, func.getType()));
				
				addr = ((Integer)((FunctionDeclaration)d).accept(this)).intValue();
			}	
		}

		if(list2.size() > 0)
			for(StatOp s: list2){
				addr = ((Integer)s.accept(this)).intValue();
			}
		
		if(isFunction){
			addr = vm.addInstr(Codes.RETURN,size);
		}
		
		if(isFunction || isFunctionDeclaration)
			ll.removeFirst();
		
		return new Integer(addr);
	}
	
	@Override
	public Object visit(FunctionDeclaration fd) {

		return fd.getBody().accept(this);
		
	}
		
	@Override
	public Object visit(VarDeclList list) {
		return new Integer(-99);
	}
		
	@Override
	public Object visit(VarDeclaration v) {
		return new Integer(-99);
	}
		
	@Override
	public Object visit(AssignOp a) {
		int addr;
		VarSemantica localVar; 
		String str = ((VarOp)a.getOperand1()).getName();
		HashMap hm = (HashMap)ll.getFirst();
		
		localVar = (VarSemantica)hm.get(str);
		a.getOperand2().accept(this);
		a.getOperand1().setType(localVar.getType());
		
		if(a.getOperand1().getType() != Type.wrongType && a.getOperand2().getType() != Type.wrongType)
			if(a.getOperand1().getType() != a.getOperand2().getType())
					System.out.println("ERRO NA ANALISE SEMANTICA DE ATRIBUICAO: OS TIPOS DA OPERACAO NAO SAO COMPATIVEIS Tipo 1:"+
							a.getOperand1().getType() + " Tipo 2: " +
							a.getOperand2().getType());
		
		addr = vm.addInstr(Codes.STVAR,localVar.getAddr());
		
		return new Integer(addr);
	}
	
	@Override
	public Object visit(ConstOp n) {
		int addr;
		
		addr = vm.addInstr(Codes.INTCONST,Integer.parseInt(n.getValue()));
		
		return new Integer (addr);
	}
		
	@Override
	public Object visit(VarOp v) {
		int addr;
		VarSemantica localVar; 
		String str = v.getName();
		HashMap hm = (HashMap)ll.getFirst();
		
		localVar = (VarSemantica)hm.get(str);
		
		v.setType(localVar.getType());
		
		addr = vm.addInstr(Codes.LDVAR,localVar.getAddr());
		
		return new Integer(addr);
	}
		
	@Override
	public Object visit(FunctionCallStat fc) {
		
		HashMap hm = (HashMap)ll.getLast();
		
		int addr;
		
		FuncSemantica func = ((FuncSemantica)hm.get(fc.getName()));
		
		fc.setType(func.getType());
		
		if(func.getType() != Type.voidType)
			vm.addInstr(Codes.INCT, 1);
		
		for(Expression e : fc.getParmList())
			e.accept(this);
		
		addr = vm.addInstr(Codes.CALL, ((FuncSemantica)hm.get(fc.getName())).getAddr());
			
		if(fc.getParmList().size() > 0)
			addr = vm.addInstr(Codes.INCT, -fc.getParmList().size());
		
		return new Integer(addr);
	}
	
	@Override
	public Object visit(ReturnStat r) {
		HashMap hm = (HashMap)ll.getFirst();
		
		if(((VarSemantica)hm.get("return")).getType() != Type.voidType){
			r.getExpression().accept(this);
			return new Integer(vm.addInstr(Codes.STVAR, ((VarSemantica)hm.get("return")).getAddr()));
		}else
			return new Integer(vm.getPC());
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	


	@Override
	public Object visit(Type t) {
		return new Integer(-99);
	}

	@Override
	public Object visit(VectorType vt) {
		return new Integer(-99);
	}

	@Override
	public Object visit(UnaryOp u) {
		return new Integer(-99);
	}

	@Override
	public Object visit(TupleOp t) {
		return new Integer(-99);
	}

	@Override
	public Object visit(WhileStat wh) {
		return new Integer(-99);
	}

	@Override
	public Object visit(FunctionCallOp fc) {
		return new Integer(-99);
	}

	@Override
	public Object visit(IndexOp idx) {
		return new Integer(-99);
	}

	public class VarSemantica {
		
		public Integer addr;
		public Type tipo;
		
		public VarSemantica(int addr, Type tipo){
			this.addr = new Integer(addr);
			this.tipo = tipo;
		}
		
		public int getAddr(){
			return addr.intValue();
		}
		
		public Type getType(){
			return tipo;
		}
		
	}
	
	public class FuncSemantica {
		
		public Integer addr;
		public Type tipo;
		public LinkedList<VarDeclaration> param;
		
		public FuncSemantica(int addr, Type tipo, LinkedList<VarDeclaration> param){
			this.addr = new Integer(addr);
			this.tipo = tipo;
			this.param = param;
		}
		
		public int getAddr(){
			return addr.intValue();
		}
		
		public Type getType(){
			return tipo;
		}
		
		public LinkedList<VarDeclaration> getParam(){
			return param;
		}
		
		public void setAddr(int addr){
			this.addr = addr;
		}
		
	}
}
